package com.iflytek.cosmo.ocr.support.license;

import javax.crypto.Cipher;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.core.util.ArrayUtil;
import com.iflytek.cosmo.ocr.common.enums.LicenseEnum;
import com.iflytek.cosmo.ocr.support.license.client.AbstractClientLicenseProcessor;
import com.iflytek.cosmo.ocr.support.license.client.ClientLicense;
import com.iflytek.cosmo.ocr.support.license.client.v1.ClientLicenseProcessorV1;
import com.iflytek.cosmo.ocr.support.license.client.v1.ClientLicenseV1;
import com.iflytek.cosmo.ocr.support.license.server.AbstractServerLicenseProcessor;
import com.iflytek.cosmo.ocr.support.license.server.ServerLicense;
import com.iflytek.cosmo.ocr.support.license.server.v1.ServerLicenseProcessorV1;
import com.iflytek.cosmo.ocr.support.license.server.v1.ServerLicenseV1;
import com.iflytek.cosmo.ocr.support.license.server.v2.ServerLicenseProcessorV2;
import com.iflytek.cosmo.ocr.support.license.server.v2.ServerLicenseV2;
import org.springframework.core.io.ClassPathResource;

/**
 * 生成密钥对(keyPair), 并保存到密钥库(keyStore):
 * <code>
 * keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -validity 36500 -alias viewOcrCertificate
 * -storepass 'iflytek!23' -dname "CN=ViewOcr, OU=iFlytek, O=iFlytek, L=Hefei, ST=Anhui, C=CN"  -storetype pkcs12
 * -keystore  viewOcrKeystore.keystore
 * </code>
 * keysize与最大明文长度关系: keysize/32 - 11, keysize: 2048 对应最长明文55字节
 * </p>
 * 导出数字证书(内有公钥):
 * <code>
 *     keytool -exportcert -alias viewOcrCertificate -keystore viewOcrKeystore.keystore -file viewOcrCertificate.cer -rfc
 * </code>
 * </p>
 * 查看密钥对:
 * <code>
 *  keytool -list -keystore ~/Downloads/viewOcrKeystore.keystore -storepass 'iflytek!23' -v
 * </code>
 * @author <a href=mailto:ktyi@iflytek.com>伊开堂</a>
 * @date 2021/2/3
 */
public class LicenseMaker {
    private static final Map<LicenseVersion, AbstractClientLicenseProcessor> clientProcessors = new HashMap<>();
    private static final Map<LicenseVersion, AbstractServerLicenseProcessor> serverProcessors = new HashMap<>();
    public static final int MAX_DATA_SIZE = 53;
    static {
        ClientLicenseProcessorV1 c1 = new ClientLicenseProcessorV1();
        clientProcessors.put(c1.supportVersion(), c1);

        ServerLicenseProcessorV1 s1 = new ServerLicenseProcessorV1();
        serverProcessors.put(s1.supportVersion(), s1);
        ServerLicenseProcessorV2 s2 = new ServerLicenseProcessorV2();
        serverProcessors.put(s2.supportVersion(), s2);
    }

    private static final String KEYSTORE_PATH = "viewOcrKeystore.keystore";
    private static final String STORE_PASS = "iflytek!23";
    private static final String ALIAS = "viewOcrCertificate";

    private static KeyStore keyStore;
    private static PrivateKey privateKey = null;
    private static X509Certificate certificate = null;

    static {
        try {
            keyStore = getKeyStore(STORE_PASS, KEYSTORE_PATH);
            privateKey = getPrivateKey(keyStore, ALIAS, STORE_PASS);
            certificate = getCertificateByKeystore(keyStore, ALIAS);
        }
        catch (Exception e) {
            System.out.println("加载密钥库异常");
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {

        // 生成指定数量的客户端授权码 "P2D":2天, "P2DT3H4M":2天3小时4分钟 "P6H3M":6小时3分钟
        List<String> licenseList = generateClientLicenseV1(250, LicenseEnum.FORMAL, "P3650D");
        testClient(licenseList);

        // 服务端授权
        String[] macList = {"1c:ae:cb:7e:f4:b8", "6c:92:bf:69:a8:fe", "e0:00:84:2b:5d:49"};
        String encryptServerInfo = generateServlerLicenseV1(Arrays.asList(macList));

        testServer(encryptServerInfo);
    }

    public static void testServer(String encryptServerInfo) {
        ServerLicense baseInfo = AbstractServerLicenseProcessor.decodeBaseInfo(encryptServerInfo);
        AbstractServerLicenseProcessor processor = serverProcessors.get(LicenseVersion.parseFromCode(baseInfo.getVersion()));
        ServerLicense license = processor.decode(encryptServerInfo);
        System.out.println("\n服务端授权解密内容");
        System.out.println(license);
    }

    public static void testClient(List<String> licenseList) throws Exception {
        System.out.println("\n客户端授权解密内容");
        // 授权码解密
        String encodedLicense = licenseList.get(0);
        String license = decryptClient(encodedLicense).toString();
        System.out.println(license);

        // // 授权码签名
        // byte[] bytes = license.getBytes();
        // String signature = Base64Encoder.encode(sign(bytes));
        // System.out.println(signature);
        //
        // // 授权码验签
        // System.out.println(verifyClient(encodedLicense, signature));
    }

    public static String generateServlerLicenseV1(List<String> macList) throws Exception {
        ServerLicenseV1 serverLicense = new ServerLicenseV1();
        serverLicense.setCreateTime(Instant.now());
        serverLicense.setMacList(macList);
        return generateServer(serverLicense.toString());
    }

    public static String generateServlerLicenseV2(String machineId) throws Exception {
        ServerLicenseV2 serverLicense = new ServerLicenseV2();
        serverLicense.setCreateTime(Instant.now());
        serverLicense.setMachineId(machineId);
        return generateServer(serverLicense.toString());
    }

    /** 生成服务器授权信息 */
    private static String generateServer(String serverInfo) throws Exception {
        System.out.println("\n\n服务端待授权内容");
        System.out.println(serverInfo);

        System.out.println("\n服务端授权码信息");
        StringBuilder sb = new StringBuilder();
        byte[] infoBytes = serverInfo.getBytes();
        // 每53个字节加密一次, 生成base64字符串
        int times = infoBytes.length / MAX_DATA_SIZE;
        for (int i = 0; i <= times; i++) {
            int startIndex = i * MAX_DATA_SIZE;
            int endIndex = startIndex + MAX_DATA_SIZE;
            endIndex = Math.min(endIndex, infoBytes.length);
            if (startIndex == endIndex) {
                continue;
            }

            // 加密
            byte[] encodeBytes = encrypt(ArrayUtil.sub(infoBytes, startIndex, endIndex), privateKey);
            String encodeStr = Base64Encoder.encode(encodeBytes);
            sb.append(encodeStr);
        }
        String encryptServerInfo = sb.toString();
        System.out.println(encryptServerInfo);
        return encryptServerInfo;
    }

    /** 验证签名 */
    private static boolean verifyClient(String license, String signature) throws Exception {
        ClientLicense baseInfo = AbstractClientLicenseProcessor.decodeBaseInfo(license);
        AbstractClientLicenseProcessor processor = clientProcessors.get(LicenseVersion.parseFromCode(baseInfo.getVersion()));
        String decodedLicense = processor.decode(license).toString();
        return processor.verify(decodedLicense.getBytes(), Base64Decoder.decode(signature));
    }

    /** 授权码解密 */
    private static ClientLicense decryptClient(String license) {
        ClientLicense baseInfo = AbstractClientLicenseProcessor.decodeBaseInfo(license);
        AbstractClientLicenseProcessor processor = clientProcessors.get(LicenseVersion.parseFromCode(baseInfo.getVersion()));
        return processor.decode(license);
    }

    /**
     * 生成授权码
     *
     * @param count    生成数量
     * @param type     授权码类型
     * @param validity 有效期时长
     *
     * @return
     *
     * @throws Exception
     */
    public static List<String> generateClientLicenseV1(int count, LicenseEnum type, String validity)
            throws Exception {
        System.out.println("客户端授权码");
        List<String> licenseList = new ArrayList<>();
        Instant now = Instant.now();
        ClientLicenseV1 license = null;
        for (int i = 0; i < count; i++) {
            license = new ClientLicenseV1(now, count, i);
            license.setType(type == null ? LicenseEnum.FORMAL : type);
            license.setValidity(validity);

            byte[] encodeBytes = encrypt(license.toString().getBytes(), privateKey);
            String clientLicense = Base64Encoder.encode(encodeBytes);
            System.out.println(clientLicense + "\n");
            licenseList.add(clientLicense);
        }

        return licenseList;
    }

    /** 加密。注意密钥是可以获取到它适用的算法的。 */
    private static byte[] encrypt(byte[] plainText, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        return cipher.doFinal(plainText);
    }
    /** 加载密钥库，与Properties文件的加载类似，都是使用load方法 */
    private static KeyStore getKeyStore(String storepass, String keystorePath) throws Exception {
        ClassPathResource resource = new ClassPathResource("viewOcrKeystore.keystore");
        try (InputStream inputStream = resource.getInputStream()) {
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(inputStream, storepass.toCharArray());
            return keyStore;
        }
    }
    /** 获取私钥 */
    private static PrivateKey getPrivateKey(KeyStore keyStore, String alias, String password) throws Exception {
        return (PrivateKey) keyStore.getKey(alias, password.toCharArray());
    }
    /** 从证书中获取加密算法，进行签名 */
    private static byte[] sign(byte[] plainText) throws Exception {
        /** 如果要从密钥库获取签名算法的名称，只能将其强制转换成X509标准，JDK 6只支持X.509类型的证书 */
        Signature signature = Signature.getInstance(certificate.getSigAlgName());
        signature.initSign(privateKey);
        signature.update(plainText);
        return signature.sign();
    }
    /** 通过密钥库获取数字证书，不需要密码，因为获取到Keystore实例 */
    private static X509Certificate getCertificateByKeystore(KeyStore keyStore, String alias) throws KeyStoreException {
        return (X509Certificate) keyStore.getCertificate(alias);
    }
}
